home *** CD-ROM | disk | FTP | other *** search
/ Windows News 2010 Summer - Disc 1 / WN_Ete2010_CD1.iso / Onglet5 / Weezo / Weezo setup.exe / {code_appDir} / www / bittorrentSeeder.php < prev    next >
PHP Script  |  2010-05-19  |  27KB  |  821 lines

  1. <?php
  2. /**
  3.  * Bittorrent seeder
  4.  *
  5.  * PHP version 5
  6.  *
  7.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  8.  * that is available through the world-wide-web at the following URI:
  9.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  10.  * the PHP License and are unable to obtain it through the web, please
  11.  * send a note to license@php.net so we can mail you a copy immediately.
  12.  *
  13.  * @category   NA
  14.  * @package    NA
  15.  * @author     Nicolas Bruley / Peer 2 World <contact@weezo.net>
  16.  * @copyright  2005-2007 Nicolas Bruley / Peer 2 World
  17.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  18.  * @version    CVS: $Id:$
  19.  * @link       http://www.weezo.net
  20.  * @since      File available since Release 1.0.8
  21.  */
  22.  
  23. define('TIMEOUT',120);
  24. define('MIN_UNCHOKE_TIME',8);
  25. define('MAX_PROCESSED_MESSAGES_PER_LOOP',8);
  26.  
  27. $debugThreshold=LOG_DET;
  28.  
  29.  
  30. /**
  31.  * Array of bittorrent clients connections
  32.  */
  33. $connections=array();
  34.  
  35.  
  36. if($_SERVER['REMOTE_ADDR']!='127.0.0.1' || !isset($_GET['action'])) exit;
  37.  
  38. require(INCLUDE_DIR.'transferFunctions.php');
  39.  
  40.  
  41. // Close session
  42. wSession_write_close(false); unset($_SESSION);
  43.  
  44.  
  45. ob_implicit_flush();
  46. set_time_limit(0);
  47. ignore_user_abort(true);
  48.  
  49. $weezoTransfersLastUpdate=-1;
  50. $transfers=array();
  51.  
  52.  
  53.  
  54. /**
  55.  * @desc Extract needed vars from general.ini config, and release all others from memory
  56.  *
  57.  */
  58. function bittorrentSeederSetEnv(){
  59.     $_ENV['weezoGeneral']=cfMGetVar('weezoGeneral');
  60.     $var['directLinkDownloadSpeedLimit']=@$_ENV['weezoGeneral']['directLinkDownloadSpeedLimit'];
  61.     $var['bittorrentPort']=@$_ENV['weezoGeneral']['bittorrentPort'];
  62.     $var['bittorrentEnabled']=@$_ENV['weezoGeneral']['bittorrentEnabled'];
  63.     unset($_ENV['weezoGeneral']);
  64.     $_ENV['weezoGeneral']=$var;
  65.     unset($var);
  66. }
  67.  
  68. /**
  69.  * @desc debug to output
  70.  *
  71.  * @param string $message
  72.  */
  73. function out($message,$debugLevel=LOG_DBG){
  74.     global $debugThreshold;
  75.     if($debugLevel>$debugThreshold) return;
  76.     if(time()-cfMGetVar('checkingSeeder')<100) return;
  77.  
  78.     static $excl;
  79.     if(!$excl) for ($i=0;$i<31;$i++) $excl[]=chr($i);
  80.     return cfDebugSingle(str_replace($excl,'',$message));
  81.     if(cfMGetVar('debug') || $_ENV['debug']) {echo $message.'<br>'; flush();}
  82.     return;
  83. }
  84.  
  85. /**
  86.  * @desc read transfers into $tranfers array
  87.  *            reload only if necessary
  88.  */
  89. function btLoadTransfers(){
  90.     global $transfers;
  91.     global $weezoTransfersLastUpdate;
  92.  
  93.     if($weezoTransfersLastUpdate==cfMGetVar('weezoTransfersLastUpdate')) return;
  94.  
  95.     $transfers=tReadTransfersFiles();
  96.     $weezoTransfersLastUpdate=cfMGetVar('weezoTransfersLastUpdate');
  97. }
  98.  
  99. /**
  100.  * @desc Update and save transfer state
  101.  *
  102.  * @param string $key: $connections array key
  103.  * @param integer $state: new requested state
  104.  * @param integer $writtenBytes: nb of written bytes since last update
  105.  * @return integer: set state
  106.  */
  107. function btUpdateTransfer($key, $state, $writtenBytes=0){
  108.     global $transfers;
  109.     global $connections;
  110.     $needSave=false;
  111.  
  112.     btLoadTransfers();
  113.  
  114.     // If transfer not found, close connection
  115.     if(!isset($transfers[$connections[$key]['tid']])){
  116.         unset($connections['key']);
  117.         return null;
  118.     }
  119.     $tr=$transfers[$connections[$key]['tid']];
  120.     $prevState=$tr->status;
  121.     if($prevState==4 || $prevState==6 || $prevState==8 || $prevState==9) return $prevState;
  122.  
  123.     if($state!=$prevState){
  124.         $tr->lastUpdated=time();
  125.         $tr->status=$state;
  126.         $needSave=true;
  127.     }
  128.     if($writtenBytes) {
  129.         $tr->sentDataLength+=$writtenBytes;
  130.         $tr->updateProgress($tr->sentDataLength/$tr->size);
  131.         //out('update progress '.($tr->sentDataLength/$tr->size));
  132.         $needSave=true;
  133.     }
  134.     if($needSave && $prevState!=$state) $tr->commitToFile($transfers,true);
  135.     elseif($needSave && $prevState==$state) $tr->commitToFile($transfers,'time');
  136.     $transfers[$connections[$key]['tid']]=$tr;
  137.     return $state;
  138. }
  139.  
  140. /**
  141.  * @desc Display string in binary and/or decimal formats
  142.  *
  143.  * @param string $str
  144.  * @param boolean $returnBits : show bits formated string
  145.  * @param boolean $returnBytes : show decimal formated string
  146.  * @return string
  147.  */
  148. function stringToBits($str, $returnBits=true, $returnBytes=true){
  149.     $output='';
  150.     if($returnBits){
  151.         for($i=0;$i<strlen($str);$i++){
  152.             $bits=8;
  153.             $a=ord($str[$i]);
  154.             for($offset=pow(2,$bits-1);$offset>=1;$offset=$offset/2) $output.=(($a & $offset)?'1':'0');
  155.             $output.= ' ';
  156.         }
  157.         if($returnBytes) $output.= '<br>';
  158.     }
  159.  
  160.     if($returnBytes){
  161.         for($i=0;$i<strlen($str);$i++){
  162.             $output.= ord($str[$i]).' ';
  163.         }
  164.     }
  165.     return $output;
  166. }
  167.  
  168. /**
  169.  * @desc return a bittorrent formated message
  170.  *
  171.  * @param integer $id : id (type) of message
  172.  * @param string $payload : message's payload
  173.  * @return string
  174.  */
  175. function btMessage($id,$payload=''){
  176.     if($id===null) return chr(0).chr(0).chr(0).chr(0);
  177.     return pack('N',strlen($payload)+1).chr($id).$payload;
  178. }
  179.  
  180. /**
  181.  * @desc generate bitfield message for complete torrent
  182.  * @param integer $pieces : number of pieces in torrent
  183.  *
  184.  * @return string bittorrent formated message
  185.  */
  186. function btMessageBitfield($pieces){
  187.     return btMessage(5, str_pad('',floor($pieces/8),chr(255)).(($pieces%8)?chr((255)>>(8-$pieces%8)<<(8-$pieces%8)):''));
  188. }
  189.  
  190. /**
  191.  * @desc Label of bittorrent message (used for debugging)
  192.  *
  193.  * @param integer $id : bittorrent id of message
  194.  * @return string description
  195.  */
  196. function btIdLabel($id){
  197.     switch ($id){
  198.         case 0:
  199.             return 'choke';
  200.         case 1:
  201.             return 'unchoke';
  202.         case 2:
  203.             return 'interested';
  204.         case 3:
  205.             return 'not interested';
  206.         case 4:
  207.             return 'have';
  208.         case 5:
  209.             return 'bitfield';
  210.         case 6:
  211.             return 'request';
  212.         case 7:
  213.             return 'piece';
  214.         case 8:
  215.             return 'cancel';
  216.         default:
  217.             return 'unknown id ('.$id.')';
  218.     }
  219. }
  220.  
  221. /**
  222.  * @desc Format bittorrent message for visual debugging
  223.  *
  224.  * @param integer $id : bittorrent id of message
  225.  * @param string $payload : bittorrent message payload
  226.  * @return string
  227.  */
  228. function btFormatedPayload($id, $payload){
  229.     switch ($id){
  230.         case 0:// choke
  231.         case 1:// unchoke
  232.         case 2:// interested
  233.         case 3:// not interested
  234.             if(strlen($payload)) return 'ERROR : no payload expected ! : '.$payload;
  235.             return 'no payload';
  236.         case 4: // have : 32 bit integer, index of piece
  237.             if(strlen($payload)!=4) return 'ERROR : incorrect payload size, 4 bytes expected ! : '.$payload;
  238.             list($len)=array_values(unpack('N',$payload));
  239.             return $payload.' => Piece n░'.$len;
  240.         case 5: // bitfield
  241.             return stringToBits($payload,true,false);
  242.         case 6: // request
  243.             if(strlen($payload)!=12) return 'ERROR : incorrect payload size, 3*4 bytes expected for REQUEST message ! : , '.strlen($payload).' provided :'.$payload;
  244.             list($index,$begin,$length)=array_values(unpack('N3l',$payload));
  245.             return 'Piece index : '.$index.', begin : '.$begin.', length : '.$length;
  246.         case 7: // piece
  247.             if(strlen($payload)<8) return 'ERROR : incorrect payload size, >= 2*4 bytes expected for PIECE message ! : '.$payload;
  248.             list($index,$begin)=array_values(unpack('N2l',substr($payload,0,8)));
  249.             return 'Piece index : '.$index.', begin : '.$begin.', DATA ('.(strlen($payload)-8).') Bytes';
  250.         case 8:
  251.             if(strlen($payload)!=12) return 'ERROR : incorrect payload size, 3*4 bytes expected for CANCEL message! : '.$payload;
  252.             list($index,$begin,$length)=array_values(unpack('N3l',$payload));
  253.             return 'Cancel index : '.$index.', begin : '.$begin.', length : '.$length;
  254.         default:
  255.             return 'Unknown format : '.$payload;
  256.     }
  257. }
  258.  
  259. /**
  260.  * @desc process incoming bittorrent message
  261.  *
  262.  * @param string $key : key of connection in $connections array
  263.  * @param integer $id : bittorrent id of message
  264.  * @param string $payload : bittorrent message payload
  265.  * @return null if connection has been shutdown
  266.  */
  267. function btProcessMessage($key, $id, $payload){
  268.     global $connections;
  269.     static $debug=0;
  270.     if($id==4) {
  271.         $debug++;
  272.         out ($debug.' PIECES RECEIVED');
  273.     }
  274.     switch ($id){
  275.         case 0:
  276.         case 1:
  277.         case 2:
  278.         case 3:
  279.         case 4:
  280.         case 5:
  281.             return false;
  282.  
  283.         case 6: // request
  284.             if(strlen($payload)!=12) return false;
  285.  
  286.             // Parse payload
  287.             list($index,$begin,$length)=array_values(unpack('N3l',$payload));
  288.  
  289.             // Update speed limitation if needed
  290.             if(@$connections[$key]['transferLimitationType']=='global'){
  291.                 $connections[$key]['transferLimitation']->setSpeedLimit(cfGGetVar('directLinkDownloadSpeedLimit')*1024);
  292.             }
  293.  
  294.             // Check speed limitation
  295.             if($connections[$key]['transferLimitation'] && !$connections[$key]['transferLimitation']->isReady()){
  296.                 out('CHOKE client',LOG_DET);
  297.                 // return btMessage(8, pack('N',$index).pack('N',$begin).pack('N',$length)); // cancel request
  298.                 // Clear pending messages and in-buffer
  299.                 $connections[$key]['choked']=time();
  300.                 $connections[$key]['inBuffer']='';
  301.                 $connections[$key]['inMessages']=array();
  302.                 // CHOKE request
  303.                 return btMessage(0);
  304.             }
  305.  
  306.  
  307.             // Load requested piece in memory (if needed)
  308.             if($index!=$connections[$key]['inMemoryPiece']){
  309.  
  310.                 $pl=$connections[$key]['pieceLength'];
  311.                 // Erase previous cached data
  312.                 $connections[$key]['inMemoryPieceData']='';
  313.                 $start=$pl*$index;
  314.                 $read=0;
  315.                 $readStarted=false;
  316.  
  317.                 // Browse files
  318.                 foreach ($connections[$key]['filesOffsets'] as $sk=>$fo) {
  319.                     $offset=0;
  320.                     // Find first file
  321.                     if(!$readStarted && $fo['end']>=$start && $fo['start']!=$fo['end']){
  322.                         $readStarted=true;
  323.                         out('Start sending data from '.$fo['name'],LOG_DET);
  324.                         $offset=$start-$fo['start'];
  325.                     }
  326.                     // Read
  327.                     if($readStarted){
  328.                         $toRead=min((1+$fo['end']-$fo['start'])-$offset,$pl-$read);
  329.                         if(0){
  330.                             if(!$handle=fopen($fo['name'],'rb')){}
  331.                             fseek($handle,$offset,SEEK_SET);
  332.                             $connections[$key]['inMemoryPieceData'].=fread($handle,$toRead);
  333.                             fclose($handle);
  334.                         }
  335.                         else{
  336.                             if(!($readData=cfFread($fo['name'],$offset,$toRead))){
  337.                                 //cfDebugSingle('FAILURE '.strlen($readData).'/'.$toRead.' at:'.($offset).' MB from '.$fo['name']);
  338.                                 out('Error : cannot open source file : '.$fo['name'],LOG_INF);
  339.                                 socket_close($connections[$key]['cnx']);
  340.                                 unset($connections[$key]);
  341.                                 return null;
  342.                             }
  343.                             //cfDebugSingle('Read '.strlen($readData).'/'.$toRead.' at:'.($offset).' MB from '.$fo['name']);
  344.                             $connections[$key]['inMemoryPieceData'].=$readData;
  345.                         }
  346.                         out('add '.$toRead.' bytes from '.$fo['name']);
  347.                         $read+=$toRead;
  348.                         // All data red
  349.                         if($read==$pl) break;
  350.                     }
  351.                 }
  352.                 $connections[$key]['inMemoryPiece']=$index;
  353.             }
  354.             // Update transfer (running, data sent)
  355.             if(btUpdateTransfer($key, 3, strlen(substr($connections[$key]['inMemoryPieceData'],$begin,$length)))===null) return null;
  356.  
  357.             // Return data
  358.             return btMessage(7,pack('N',$index).pack('N',$begin).substr($connections[$key]['inMemoryPieceData'],$begin,$length));
  359.  
  360.         case 7: // receive piece : unsupported
  361.             out('Receive piece (unsupported, dropped)');
  362.             return false;
  363.         case 8: // cancel
  364.             if(strlen($payload)!=12) return false;
  365.             // Transform cancel message into matching request message to find it into messages list
  366.             $searched=chr(8).$payload;
  367.             // browse in-messages for canceled request
  368.             foreach ($connections[$key]['inMessages'] as $msgKey=>$msg) if($msg==$searched){
  369.                 unset($connections[$key]['inMessages'][$msgKey]);
  370.                 out('Request successfully canceled',LOG_INF);
  371.                 return false;
  372.             }
  373.             out('Request couldn\'t be canceled',LOG_INF);
  374.             return false;
  375.         default:
  376.             out('Drop unknown message id='.$id,LOG_INF);
  377.             return false;
  378.     }
  379. }
  380.  
  381. /**
  382.  * @desc Write connection outgoing buffer
  383.  *
  384.  * @param string $key : id of connection to use, in $connections array
  385.  * @param string $data : data to be queued to buffer
  386.  * @desc return remaining bytes in buffer, or false if socket has been closed
  387.  */
  388. function btSocket_write($key, $data=''){
  389.     global $connections;
  390.  
  391.     // Transfer speed limitation, add data length to transfer limitation
  392.     if($connections[$key]['transferLimitation']) $connections[$key]['transferLimitation']->addData(strlen($data),false,false);
  393.  
  394.     // Queue data in buffer
  395.     $connections[$key]['outBuffer'].=$data;
  396.     // If buffer empty, exit
  397.     if(!strlen($connections[$key]['outBuffer'])) return 0;
  398.     socket_clear_error($connections[$key]['cnx']);
  399.     // Write
  400.     $connections[$key]['outBuffer']=substr($connections[$key]['outBuffer'],@socket_write($connections[$key]['cnx'],$connections[$key]['outBuffer']));
  401.     if(strlen($connections[$key]['outBuffer'])) out('Send '.strlen($connections[$key]['outBuffer']).' bytes to client');
  402.  
  403.     // Detect closed connection
  404.     if(socket_last_error($connections[$key]['cnx'])==10053){
  405.         socket_close($connections[$key]['cnx']);
  406.         // Set transfer as paused
  407.         btUpdateTransfer($key, 5, 0);
  408.         out('Socket closed on write error',LOG_DET);
  409.         unset($connections[$key]);
  410.         return NULL;
  411.     }
  412.     return strlen($connections[$key]['outBuffer']);
  413. }
  414.  
  415. /**
  416.  * @process data received from connection
  417.  *
  418.  * @param string: $key : connection key
  419.  */
  420. function btProcessInBuffer($key){
  421.     global $transfers;
  422.     global $connections;
  423.     global $seederId;
  424.  
  425.     // Empty buffer => exit
  426.     if(!$connections[$key]['inBuffer']) return;
  427.     while(true){
  428.         $buff=$connections[$key]['inBuffer'];
  429.  
  430.         /**
  431.          * Bittorrent handshake
  432.          */
  433.         if(strlen($buff)>=48 && ord($buff[0])==19 && strtolower(substr($buff,1,19))=='bittorrent protocol'){
  434.             out('Bittorrent handshake detected',LOG_DET);
  435.             $connections[$key]['type']='bittorrent';
  436.  
  437.             // Update connection
  438.             if(strlen($buff)>=68){
  439.                 $message=substr($buff,0,68);
  440.                 $connections[$key]['inBuffer']=substr($buff,68);
  441.                 $torrentIdHash=str_replace('-','%2D',str_replace('.','%2E',str_replace('_','%5F',strtoupper(rawurlencode(substr($message,28,20))))));
  442.                 $clientIdHash=substr($message,48,20);
  443.             }
  444.             else{
  445.                 $message=substr($buff,0,48);
  446.                 $torrentIdHash=str_replace('-','%2D',str_replace('.','%2E',str_replace('_','%5F',strtoupper(rawurlencode(substr($message,28,20))))));
  447.                 $clientIdHash=false;
  448.             }
  449.  
  450.             btLoadTransfers();
  451.  
  452.             // Seek matching torrent in tranfers
  453.             $found=false;
  454.             foreach ($transfers as $trId=>$tr) {
  455.                 if(strtolower(rawurldecode($torrentIdHash))==strtolower(rawurldecode($tr->infoHash))){
  456.                     if($tr->status!=6 && $tr->status!=8 && $tr->status!=9){
  457.                         $connections[$key]['tid']=$trId;
  458.                         $found=true;
  459.                         out('Requested torrent found',LOG_INF);
  460.                         out($tr->detail(),LOG_INF);
  461.                         break;
  462.                     }
  463.                 }
  464.             }
  465.  
  466.             // If not found in transfers, hang-up
  467.             if(!$found){
  468.                 out('Requested torrent not found',LOG_ER);
  469.                 socket_close($connections[$key]['cnx']);
  470.                 unset($connections[$key]);
  471.                 return false;
  472.             }
  473.  
  474.             // Load transfer data into torrent
  475.             $connections[$key]['pieceLength']=$tr->pieceLength;
  476.             $filesOffsets=explode('|',$tr->filesOffsets);
  477.             $startOffset=0;
  478.             foreach (explode('|',$tr->filesOffsets) as $fo) if(strlen($fo)){
  479.                 list($completeFilename,$endOffset)=explode('?',$fo);
  480.                 $connections[$key]['filesOffsets'][]=array('name'=>$completeFilename,'start'=>$startOffset,'end'=>$endOffset-1);
  481.                 out($completeFilename.' File Offset:'.$startOffset.'->'.$endOffset,LOG_INF);
  482.                 $startOffset=$endOffset;
  483.             }
  484.  
  485.  
  486.             /**
  487.              * Transfer speed limitation
  488.              */
  489.  
  490.             // No user Id: use global direct link speed limitation
  491.             if(!$tr->userId) {
  492.                 $connections[$key]['transferLimitationType']='global';
  493.                 $speedLimit=cfGGetVar('directLinkDownloadSpeedLimit');
  494.             }
  495.             // Passed user ID => search for speed limit for this user
  496.             else{
  497.                 $connections[$key]['transferLimitationType']='user';
  498.                 $users=cfMGetVar('weezoUsers');
  499.                 if(isset($users[$tr->userId]) && isset($users[$tr->userId]['downloadSpeedLimit']))
  500.                     $speedLimit=$users[$tr->userId]['downloadSpeedLimit'];
  501.                 unset($users);
  502.             }
  503.  
  504.             if(@$speedLimit){
  505.                 $connections[$key]['transferLimitation']=new transferLimitation(1024*$speedLimit);
  506.                 out('SET LIMITATION '.$speedLimit.' KB/s',LOG_INF);
  507.             }
  508.  
  509.             // Handshake response
  510.             $handShakeResponse=substr($message,0,20).str_pad('',8,chr(0)).substr($message,28,20).str_pad($seederId,20,chr(0));
  511.             if(!$connections[$key]['handshaked']){
  512.                 if(btSocket_write($key,$handShakeResponse)===null) return;
  513.                 out('>>>> handshake response:'.$handShakeResponse);
  514.                 $connections[$key]['handshaked']=true;
  515.             }
  516.  
  517.             // Update transfer state (connecting)
  518.             btUpdateTransfer($key, 7, 0);
  519.  
  520.             // Partial handshake received : don't send anything else than handshake
  521.             if(!$clientIdHash) return;
  522.  
  523.             // Send bitfield
  524.             if(btSocket_write($key,btMessageBitfield($tr->pieces))===null) return;
  525.             out('>>>> bitfield : '.btMessageBitfield($tr->pieces));
  526.  
  527.             // Unchoke client
  528.             if(btSocket_write($key,btMessage(1))===null) return;
  529.             out('>>>> unchoke : '.btMessage(1));
  530.  
  531.             // Set myself as not interested
  532.             //if(btSocket_write($key,btMessage(3))===null) return;
  533.             //out(socket_last_error($connections[$key]['cnx']).' '.socket_strerror(socket_last_error($connections[$key]['cnx'])));
  534.             //out('>>>> not interested : '.btMessage(3));
  535.  
  536.             continue;
  537.         }
  538.  
  539.  
  540.         /**
  541.          * Bittorrent message
  542.          */
  543.         if((true || $connections[$key]['type']=='bittorrent') && strlen($buff)>=4){
  544.             list($len)=array_values(unpack('N',substr($buff,0,4)));
  545.             $buff=substr($buff,4);
  546.             if($len>strlen($buff)) break; // message length < announced message length :
  547.  
  548.             // Non-empty message
  549.             if($len){
  550.                 $id=ord(substr($buff,0,1));
  551.                 $payload=substr($buff,1,$len-1);
  552.                 out('Add to msg list: '.$id.'('.btIdLabel($id).') = "'.btFormatedPayload($id,$payload).'" ('.(strlen($payload)).')');
  553.  
  554.                 // If "cancel" message, proceed now (remove matching piece request from messages stack)
  555.                 if($id==8) btProcessMessage($key,$id,$payload);
  556.                 // else, add message to message stack
  557.                 else $connections[$key]['inMessages'][]=chr($id).$payload;
  558.             }
  559.             // Empty message = keep alive
  560.             else {
  561.                 $id=null;
  562.                 $payload=false;
  563.                 out('BT message OK : keep alive');
  564.             }
  565.  
  566.             // Remove processed data from in-buffer
  567.             $connections[$key]['inBuffer']=substr($connections[$key]['inBuffer'],4+$len);
  568.  
  569.             continue;
  570.         }
  571.         break;
  572.     }
  573. }
  574.  
  575. /**
  576.  * @process bittorrent messages stack
  577.  *
  578.  * @param string: $key : connection key
  579.  * @return connection : processed connection
  580.  */
  581. function btProcessMessages($key){
  582.     global $connections;
  583.     $processed=0;
  584.     while ($processed<MAX_PROCESSED_MESSAGES_PER_LOOP && count($connections[$key]['inMessages'])){
  585.         $message=array_shift($connections[$key]['inMessages']);
  586.         $id=ord(substr($message,0,1));
  587.         $payload=substr($message,1);
  588.         out('Buff: '.count($connections[$key]['inMessages']).' msgs; Process '.$id.'=>'.btFormatedPayload($id,$payload));
  589.         // Process message
  590.         if($response=btProcessMessage($key,$id,$payload)) {
  591.             out('>>>> '.btFormatedPayload(ord(substr($response,4,1)),substr($response,5)));
  592.             if(btSocket_write($key,$response)===null) return false;
  593.         }
  594.         // If connection has been shutdown, exit
  595.         if($response===null) return false;
  596.     }
  597. }
  598.  
  599. // Release unneeded env vars
  600. bittorrentSeederSetEnv();
  601.  
  602. // Stop server
  603. if($_GET['action']=='stop'){
  604.     echo '<head><title>STOP</title></head><body>';
  605.     if(isset($_GET['bittorrentPort'])) $port=$_GET['bittorrentPort']; else $port=cfGGetVar('bittorrentPort');
  606.     echo 'Send stop request on port '.$port.'<br>';
  607.     if($handle=@fsockopen('127.0.0.1',$port,$errno,$errstr,4)) {
  608.         fwrite($handle,'stopBittorrentSeeder');
  609.         $data=''; while (!feof($handle)) $data.=fread($handle,1024);
  610.         fclose($handle);
  611.         if(isset($_GET['app'])) echo 'OK'; else echo $data;
  612.     }
  613.     else echo 'NOK';
  614. }
  615. if(isset($_GET['debug'])) $_ENV['debug']=true; else $_ENV['debug']=false;
  616. if($_GET['action']!='start' || (!cfGGetVar('bittorrentEnabled') && !isset($_GET['debug']))) exit;
  617.  
  618.  
  619.  
  620. // SERVER SCRIPT
  621.  
  622. // Create socket
  623. if(!$serverSock = socket_create(AF_INET,SOCK_STREAM,SOL_TCP)) die('<head><title>BITTORRENT (STOPPED)</title></head><body>Error creating socket');
  624. if(!@socket_bind($serverSock,'0.0.0.0',cfGGetVar('bittorrentPort'))) die('<head><title>BITTORRENT (STOPPED)</title></head><body>Port '.cfGGetVar('bittorrentPort').' already used');
  625. ;
  626. socket_listen($serverSock);
  627. socket_set_nonblock($serverSock);
  628.  
  629. echo '<head><title>BITTORRENT</title></head><body>';
  630. flush();
  631.  
  632. out("----------------------------------------------------------------------------------------",LOG_INF);
  633. out("Server started on port ".cfGGetVar('bittorrentPort'),LOG_INF);
  634. out('Debug activated: ' );
  635. cfLog('BitTorrent Seeder started',2);
  636.  
  637. // Set seeder ID
  638. if($regInfo=cfIsRegistered()) $seederId=substr('-WZ0000-'.$regInfo['regName'],0,20); else $seederId='-WZ0000-unRegistered';
  639.  
  640.  
  641. $i=0;
  642. $shutdown=false;
  643.  
  644. //set_socket_blocking($serverSock,0);
  645.  
  646.  
  647. /****************************************************************************************************************
  648.  * MAIN LOOP
  649.  ***************************************************************************************************************/
  650. while(!$shutdown) {
  651.     /**
  652.      * New connection
  653.      */
  654.     if($c = @socket_accept($serverSock)) {
  655.         if(!@socket_getpeername ($c, $addr, $port)) $addr=false;
  656.         $connections[]=array(
  657.             'tid'=>false, // Connection Id, matches with tranfer Id
  658.             'cnx'=>$c, // Connection socket
  659.             'remoteIp'=>$addr, // Client's IP
  660.             'lastPing'=>time(), // Last message received from client, timestamp
  661.             'type'=>null, // Connection type (='bittorrent' if handshake detected)
  662.             'inBuffer'=>'', // Current unprocessed buffer (received from client)
  663.             'inMessages'=>array(), // Current unprocessed messages (received from client)
  664.             'outBuffer'=>'', // Current unprocessed buffer (received from client)
  665.             'pieceLength'=>0, // Size of pieces, in bytes
  666.             'filesOffsets'=>array(), // Array of files name,start offset, end offset
  667.             'inMemoryPiece'=>-1, // n░ of piece loaded into memory
  668.             'inMemoryPieceData'=>false, // data of piece loaded into memory
  669.             'handshaked'=>false, // True if seeder has returned handshake
  670.             'choked'=>false, // True if seeder is choked (overspeed)
  671.             'transferLimitation'=>false, // Transfer speed limitation object
  672.             'transferLimitationType'=>false // Transfer speed limitation type : 'global' to use cfGGetVar('directLinkDownloadSpeedLimit'), 'user' to use user's value
  673.             );
  674.         out('******** New Connection ********* from '.$addr,LOG_INF);
  675.     }
  676.  
  677.     /**
  678.      * Process opened connections
  679.      */
  680.     foreach ($connections as $key=>$connection){
  681.         $buff='';
  682.         $cnx=$connection['cnx'];
  683.         socket_clear_error($cnx);
  684.  
  685.         // Try to read data
  686.         try{$buff=socket_read($cnx,1024);} catch(Exception $e){out('Socket read error:'.$e->getMessage(),LOG_INF);};
  687.         //out('read '.strlen($buff).' bytes',LOG_DET );
  688.  
  689.         // Socket closed or timeout detection
  690.         if(socket_last_error($cnx)==10053 || ($buff=='' && socket_last_error($cnx)==0) || (time()-$connection['lastPing'] > TIMEOUT)) {
  691.             if(time()-$connection['lastPing'] > TIMEOUT) out("timeout disconnected",LOG_INF); else out('read disconnected (last error:'.socket_last_error($cnx).')',LOG_INF);
  692.             socket_shutdown($cnx,2);
  693.  
  694.             btLoadTransfers();
  695.  
  696.             // Update transfer state (paused)
  697.             if(isset($transfers[$connections[$key]['tid']])) {
  698.                 if($transfers[$connections[$key]['tid']]->status!=4 && $transfers[$connections[$key]['tid']]->status!=6 && $transfers[$connections[$key]['tid']]->status!=8 && $transfers[$connections[$key]['tid']]->status!=9) btUpdateTransfer($key, 5, 0);
  699.             }
  700.             unset($connections[$key]);
  701.             continue;
  702.         }
  703.  
  704.         /**
  705.          * Data received from client
  706.          */
  707.         if(strlen($buff)){
  708.             // Server Shutdown command
  709.             if($buff=='stopBittorrentSeeder' && $connection['remoteIp']=='127.0.0.1'){
  710.                 @socket_write($cnx,'<b style="color:white;background:#'.rand(0,999).'">Shutdown command received</b> '.time());
  711.                 socket_shutdown($cnx,2); $shutdown=true; break;
  712.             }
  713.  
  714.             // Server ping command
  715.             if($buff=='pingBittorrentSeeder' && $connection['remoteIp']=='127.0.0.1'){
  716.                 @socket_write($cnx,'ok');
  717.                 socket_shutdown($cnx,2); $shutdown=true; break;
  718.             }
  719.  
  720.             // Debug output received message
  721.             out('<<<< '.$buff);
  722.  
  723.             // Update lastPing
  724.             $connections[$key]['lastPing']=time();
  725.  
  726.             // Completed torrent message
  727. //            if(stripos($buff,'torrentcompleted')!==false) cfDbg('!!!');
  728.             if(strlen($buff)==36 && substr($buff,0,16)=='torrentCompleted'){
  729.                 out('TRANSFER COMPLETED MESSAGE RECEIVED',LOG_INF);
  730.  
  731.                 $infoHash=str_replace('-','%2D',str_replace('.','%2E',str_replace('_','%5F',rawurlencode(substr($buff,16)))));
  732.                 // Load transfers
  733.                 btLoadTransfers();
  734.  
  735.                 // And search for the one matching the hash
  736.                 foreach ($transfers as $trId=>$tr) {
  737.                     if($infoHash==$tr->infoHash){
  738.  
  739.                         out('-------TRANSFER COMPLETED---------',LOG_INF);
  740.                         // Set as finished (if single download, set as completed, else reset to 0 (created) state)
  741.                         $transfers[$trId]->status=4;
  742.                         // Completed transfers. Check if bittorrent download is linked with a directLink or a publishDownload
  743.                         // and if so, process differently (check limitations)
  744.                         if($tr->linkedTransferToken){
  745.                             require_once(INCLUDE_DIR.'explorerFunctions.php');
  746.                             $tokenList=efTokensRead();
  747.  
  748.                             $tid=$tr->linkedTransferToken;
  749.  
  750.                             if($token=@$tokenList[$tid]){
  751.                                 // Increment number of downloads for this directLink or publishDownload
  752.                                 $tokenList[$tid]['limitNb']--;
  753.                                 efTokensWrite($tokenList);
  754.  
  755.                                 // If token is still valid, reset bittorrent transfer instead of setting it completed
  756.                                 if(efTokenIsValid($tokenList[$tid])){
  757.                                     $state=0;
  758.                                     $transfers[$trId]->trackerIdSet=false;
  759.                                     $transfers[$trId]->sentDataLength=0;
  760.                                     $transfers[$trId]->updateProgress(0);
  761.                                 }
  762.                                 // Else, remove from list
  763.                                 else{
  764.                                     $tr->clear(true);
  765.                                 }
  766.                             }
  767.                             unset($tokenList);
  768.                         }
  769.                         $tr->commitToFile($transfers,true);
  770.                         break;
  771.                     }
  772.                 }
  773.  
  774.  
  775.  
  776.                 continue;
  777.             }
  778.         }
  779.  
  780.         /**
  781.          * Process buffers and messages stacks
  782.          */
  783.  
  784.         // Unchoke client if needed
  785.         if($connections[$key]['choked'] && (time() - $connections[$key]['choked'] > MIN_UNCHOKE_TIME) && $connections[$key]['transferLimitation']->isReady()) {
  786.             $connections[$key]['choked']=false;
  787.             out('Unchoke client');
  788.             if(btSocket_write($key,btMessage(1))===null) continue;
  789.         }
  790.  
  791.         // Concatenate data to connection buffer if client is not choked
  792.         if(!$connections[$key]['choked']) $connections[$key]['inBuffer'].=$buff;
  793.  
  794.         // Process bittorrent in-buffer
  795.         btProcessInBuffer($key);
  796.  
  797.         // Process pending in-messages
  798.         btProcessMessages($key);
  799.  
  800.         // Send not-yet sent out-buffer (if connection not closed)
  801.         if(isset($connections[$key])) btSocket_write($key);
  802.  
  803.         // Keep alive
  804.         //socket_write($cnx,btMessage(null));
  805.     }
  806.     usleep(100000);
  807.     $i++;
  808.  
  809.     // Reload general config every 5sec (usefull for max transfer rate and maybe other params...)
  810.     if($i%50===0) {
  811.         bittorrentSeederSetEnv();
  812.         out('mem:'.memory_get_usage().' speed limit:'.$_ENV['weezoGeneral']['directLinkDownloadSpeedLimit']);
  813.     }
  814. }
  815. socket_close($serverSock);
  816. out("************************");
  817. out("Server shutdown");
  818. ?>
  819. <script type="text/javascript">
  820. document.title="BITTORRENT (STOPPED)";
  821. </script>